/*
 * Unidata Platform Community Edition
 * Copyright (c) 2013-2020, UNIDATA LLC, All rights reserved.
 * This file is part of the Unidata Platform Community Edition software.
 *
 * Unidata Platform Community Edition is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Unidata Platform Community Edition is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
 */

package org.unidata.mdm.data.service.segments.relations.get;

import java.util.Date;
import java.util.Objects;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.unidata.mdm.core.type.timeline.Timeline;
import org.unidata.mdm.data.context.GetRelationRequestContext;
import org.unidata.mdm.data.context.GetRelationTimelineRequestContext;
import org.unidata.mdm.data.dto.GetRelationDTO;
import org.unidata.mdm.data.exception.DataExceptionIds;
import org.unidata.mdm.data.exception.DataProcessingException;
import org.unidata.mdm.data.module.DataModule;
import org.unidata.mdm.data.service.DataRecordsService;
import org.unidata.mdm.data.service.impl.CommonRelationsComponent;
import org.unidata.mdm.data.service.impl.RelationDraftProviderComponent;
import org.unidata.mdm.data.service.segments.ContainmentRelationSupport;
import org.unidata.mdm.data.type.data.OriginRelation;
import org.unidata.mdm.data.type.data.RelationType;
import org.unidata.mdm.data.type.keys.RelationKeys;
import org.unidata.mdm.system.type.pipeline.Start;
import org.unidata.mdm.system.type.runtime.MeasurementPoint;

/**
 * @author Mikhail Mikhailov on Dec 4, 2019
 */
@Component(RelationGetStartExecutor.SEGMENT_ID)
public class RelationGetStartExecutor extends Start<GetRelationRequestContext, GetRelationDTO> implements ContainmentRelationSupport {
    /**
     * This segment ID.
     */
    public static final String SEGMENT_ID = DataModule.MODULE_ID + "[RELATION_GET_START]";
    /**
     * Localized message code.
     */
    public static final String SEGMENT_DESCRIPTION = DataModule.MODULE_ID + ".relations.get.start.description";
    /**
     * Logger.
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(RelationGetStartExecutor.class);
    /**
     * The CRC.
     */
    @Autowired
    private CommonRelationsComponent commonRelationsComponent;
    /**
     * RDPC.
     */
    @Autowired
    private RelationDraftProviderComponent relationDraftProviderComponent;
    /**
     * The DRS.
     */
    @Autowired
    private DataRecordsService dataRecordsService;
    /**
     * Constructor.
     */
    public RelationGetStartExecutor() {
        super(SEGMENT_ID, SEGMENT_DESCRIPTION, GetRelationRequestContext.class, GetRelationDTO.class);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public void start(GetRelationRequestContext ctx) {
        MeasurementPoint.start();
        try {
            setup(ctx);
        } finally {
            MeasurementPoint.stop();
        }
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public String subject(GetRelationRequestContext ctx) {
        setup(ctx);
        RelationKeys keys = ctx.relationKeys();
        return keys.getRelationName();
    }

    protected void setup(GetRelationRequestContext ctx) {

        if (ctx.setUp()) {
            return;
        }

        setupTimelineAndKeys(ctx);

        setupVerify(ctx);

        setupContainment(ctx);

        ctx.setUp(true);
    }

    protected void setupTimelineAndKeys(GetRelationRequestContext ctx) {

        Timeline<OriginRelation> timeline = commonRelationsComponent.ensureAndGetRelationTimeline(
                GetRelationTimelineRequestContext.builder(ctx)
                    .forDate(Objects.isNull(ctx.getForDate()) ? new Date(System.currentTimeMillis()) : ctx.getForDate())
                    .fetchData(true)
                    .build());

        ctx.currentTimeline(timeline);
        if (Objects.nonNull(timeline)) {

            RelationKeys keys = timeline.getKeys();

            // Name and type not really needed. Added just for convenience.
            ctx.relationKeys(keys);
            if (Objects.nonNull(keys)) {

                ctx.relationName(keys.getRelationName());
                ctx.relationType(keys.getRelationType());
            }
        }
    }

    protected void setupVerify(GetRelationRequestContext ctx) {

        Timeline<OriginRelation> timeline = ctx.currentTimeline();
        if (Objects.isNull(timeline) || Objects.isNull(timeline.getKeys())) {

            final String message
                = "Relation get: relation of type [{}] not found by supplied keys - relation etalon id [{}], relation origin id [{}], "
                + "etalon id: [{}], origin id [{}], external id [{}], source system [{}], name [{}]";

            LOGGER.warn(message,
                    ctx.relationName(),
                    ctx.getRelationEtalonKey(),
                    ctx.getRelationOriginKey(),
                    ctx.getEtalonKey(),
                    ctx.getOriginKey(),
                    ctx.getExternalId(),
                    ctx.getSourceSystem(),
                    ctx.getEntityName());

            throw new DataProcessingException(message, DataExceptionIds.EX_DATA_RELATIONS_GET_NOT_FOUND_BY_SUPPLIED_KEYS,
                    ctx.relationName(),
                    ctx.getRelationEtalonKey(),
                    ctx.getRelationOriginKey(),
                    ctx.getEtalonKey(),
                    ctx.getOriginKey(),
                    ctx.getExternalId(),
                    ctx.getSourceSystem(),
                    ctx.getEntityName());
        }
    }

    protected void setupContainment(GetRelationRequestContext ctx) {

        if (ctx.relationType() != RelationType.CONTAINS) {
            return;
        }

        get(ctx);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public RelationDraftProviderComponent draftProviderComponent() {
        return relationDraftProviderComponent;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public DataRecordsService dataRecordsService() {
        return dataRecordsService;
    }
}
